Framework.Comm = {}

/********************************
***     Shared networking     ***
********************************/

local DATASPLITTER = "\0"

local CommObjIn = {}
CommObjIn.__index = CommObjIn

local CommObjOut = {}
CommObjOut.__index = CommObjOut

local VarTypes = { 	{ Name = "Bool",   		 Size = 1,  Converter = function(a) return string.byte(a) == 1 end },
					{ Name = "Char",   		 Size = 1,  Converter = string.char },
					{ Name = "Short",  		 Size = 2,  Converter = Framework.Utilities.BinaryToNumber16 },
					{ Name = "Entity", 		 Size = 4,  Converter = function(a) return ents.GetByIndex( Framework.Utilities.BinaryToNumber32(a) ) end },
					{ Name = "Long",   		 Size = 4,  Converter = Framework.Utilities.BinaryToNumber32 },
					{ Name = "Float",  		 Size = 4,  Converter = Framework.Utilities.BinaryToFloat },
					{ Name = "Angle",  		 Size = 12, Converter = Framework.Utilities.BinaryToAngle }, 
					{ Name = "Vector", 		 Size = 12, Converter = Framework.Utilities.BinaryToVector},
					{ Name = "VectorNormal", Size = 12, Converter = Framework.Utilities.BinaryToVector},
					{ Name = "String", 		 Size = string.len } }

for TypeID, Type in pairs( VarTypes ) do
	CommObjIn["Add" .. Type.Name] = function(self, Value, pos)
		if( Pos ) then
			table.insert( self.Values, Pos, { Type = TypeID, Value = Value } )
		else
			table.insert( self.Values, { Type = TypeID, Value = Value } )
		end
		return true
	end
	
	CommObjOut["Read" .. Type.Name] = function( self )

		if( TypeID == 10 && self:BytesLeft() > 0 ) then
			local End = self.Data:find( DATASPLITTER, self.Cursor + 1 )
			local Str = self.Data:sub( self.Cursor + 1, End - 1 )
			self.Cursor = End+1
			return Str
		elseif( self:BytesLeft() - Type.Size >= 0 ) then
			local ret = Type.Converter( self.Data:sub( self.Cursor+1, self.Cursor+1 + Type.Size ) )
			self.Cursor = self.Cursor + Type.Size + 1
			return ret
		end
		Framework.IO.Error( "Attempted to read out-of-bounds data from Comm-Object!" )
		return false
	end
end

function CommObjOut:GetOrigin()
	return self.Origin
end

function CommObjOut:BytesLeft()
	return #self.Data - self.Cursor
end

function CommObjOut:GetType()
	return VarTypes[Framework.Utilities.CharToNumber(self.Data:sub( self.Cursor, self.Cursor ))].Name
end

function CommObjOut:Read()
	return self["Read" .. self:GetType()]( self )
end

function CommObjOut:ReadAll()
	local t = {}
	while( self:BytesLeft() > 0 ) do
		table.insert( t, self:Read() )
	end
	return t
end

local SendTypes = {
	player = "AddEntity",
	vehicle = "AddEntity",
	entity = "AddEntity",
	number = "AddFloat",
	vector = "AddVector",
	angle = "AddAngle",
	boolean = "AddBool",
	string = "AddString"
}

function CommObjIn:Add( ... )
	for k, v in pairs( {...} ) do
		local t = type( v ):lower()
		if( t == "number" ) then
			if( v % 1 != 0 ) then
				self:AddFloat( v )
			elseif( math.abs(v) > 65535 ) then
				self:AddLong( v )
			elseif( math.abs(v) > 255 ) then
				self:AddShort( v )
			else
				self:AddChar( v )
			end
		else
			if( SendTypes[t] ) then
				self[SendTypes[t]]( self, v )
			end
		end
	end
end

function CommObjIn:Clear()
	self.Values = {}
end

function CommObjIn:Remove( Index )
	table.remove( self.Values, Index )
end

if( SERVER ) then
	function CommObjIn:AddRecipient( Recipient ) 
		if( type( self.Recipients ) == "CRecipientFilter" ) then
			self.Recipients:AddPlayer( Recipient )
		else
			table.insert( self.Recipients, Recipient )
		end
	end

	function CommObjIn:RemoveRecipient( Recipient )
		if( type( self.Recipients ) == "CRecipientFilter" ) then
			self.Recipients:RemovePlayer( Recipient )
		else
			for k, v in pairs( self.Recipients ) do
				if( v == Recipient ) then
					table.remove( self.Recipients, k )
					return
				end
			end
		end
	end
end

function CommObjIn:Send( ... )
	local t = {...}
	if( #t != 0 ) then
		self:Add( ... )
	end
	
	if( #self.Values == 0 ) then
		Framework.IO.Warning( "Attempted to send empty CommObject '" .. self.Name .."' without any data" )
		return false
	end
		
	local Data = ""

	for k, v in pairs( self.Values ) do
		Data = Data .. Framework.Utilities.NumberToChar( v.Type )
		if( v.Type <= 2 ) then
			Data = Data .. Framework.Utilities.NumberToChar( v.Value )
		elseif( v.Type == 3 ) then
			Data = Data .. Framework.Utilities.NumberToBinary16( v.Value )
		elseif( v.Type == 4 ) then
			Data = Data .. Framework.Utilities.NumberToBinary32( v.Value:EntIndex() )
		elseif( v.Type == 5 ) then
			Data = Data .. Framework.Utilities.NumberToBinary32( v.Value )
		elseif( v.Type == 6 ) then
			Data = Data .. Framework.Utilities.FloatToBinary( v.Value )
		elseif( v.Type <= 9 ) then
			Data = Data .. Framework.Utilities.VectorToBinary( v.Value )
		elseif( v.Type == 10 ) then
			Data = Data .. v.Value .. DATASPLITTER
		end
	end

	Data = glon.encode( Data )
	if( SERVER ) then
		datastream.StreamToClients( self.Recipients, "~c", {n=self.Name,d=Data} )
	else
		datastream.StreamToServer( "~c", {n=self.Name,d=Data} )
	end
end

local CommHandlers = {}

local function FmwkIncData( pl, _, _, _, dec )
	if( CommHandlers[dec.n] ) then
		CommHandlers[dec.n]( setmetatable( { Origin = pl, Name = dec.n, Cursor = 1, Data = glon.decode( dec.d ) }, CommObjOut ) )
	else
		Framework.IO.Warning( "Unhandled " .. (SERVER && "Server" || "Client") .. "side Comm-Object '" .. dec.n .. "'" )
	end
end 

if( CLIENT ) then
	datastream.Hook( "~c", function( ... ) FmwkIncData( "Server", ... ) end )
else
	datastream.Hook( "~c", FmwkIncData )
end

function Framework.Comm.Register( ST_Name, FU_Func )
	CommHandlers[ST_Name] = FU_Func
end

function Framework.Comm.New( ST_Name, OP_TA_RF_Recipients )
	return setmetatable( { Name = ST_Name, Values = {}, Recipients = SERVER && (OP_TA_RF_Recipients or {}) || nil }, CommObjIn )
end


/********************************
***     Remote Event Table    ***
********************************/

local CMD_ADD, CMD_COUNT, CMD_GET, CMD_POP = 1, 2, 3, 4

local function Formatter( Contents )
	return Contents:sub(1,1) == "0", Contents:sub( 2 )
end

local function Grabber( Contents, Function )
	local b, s = Formatter( Contents )
	if( b ) then
		local t = {}
		local c = 0
		local IP, Msg, i = s:match( "</br><r><i>([%d%.]+)</i><s>(.-)</s></r>()" )
		while( IP != nil && c < 100 ) do
			s = s:sub( i )
			c = c + 1
			table.insert( t, { IP = IP, Msg = Msg } )
			IP, Msg, i = s:match( "</br><r><i>([%d%.]+)</i><s>(.-)</s></r>()" )
		end
		return true, t
	end
	return false
end

function Framework.Comm.InsertEvent( App, Unique, Str, Callback )
	http.Get( "http://errur.com/test/MessageTracker.php?c=" .. CMD_ADD .. "&a=" .. App .. "&i=" .. Unique .. "&s=" .. Framework.Utilities.URLEncode( Str ), "", function(a) if( Callback ) then Callback( Formatter( a ) ) end end )
end

function Framework.Comm.CountEvents( App, Unique, Callback )
	http.Get( "http://errur.com/test/MessageTracker.php?c=" .. CMD_COUNT .. "&a=" .. App .. "&i=" .. Unique, "", function(a) if( Callback ) then Callback( Formatter( a ) ) end end )
end

function Framework.Comm.GetEvents( App, Unique, Limit, Callback )
	Limit = Limit or 100
	http.Get( "http://errur.com/test/MessageTracker.php?c=" .. CMD_GET .. "&a=" .. App .. "&i=" .. Unique .. "&s=" .. tonumber( Limit ), "", function(a) if( Callback ) then Callback( Grabber( a ) ) end end )
end

function Framework.Comm.PopEvents( App, Unique, Limit, Callback )
	Limit = Limit or 100
	http.Get( "http://errur.com/test/MessageTracker.php?c=" .. CMD_POP .. "&a=" .. App .. "&i=" .. Unique .. "&s=" .. tonumber( Limit ), "", function(a) if( Callback ) then Callback( Grabber( a ) ) end end )
end

EvObj = {}
EvObj.__index = EvObj

function Framework.Comm.EventTracker( App, Unique )
	return setmetatable( { App = App, Unique = Unique }, EvObj )
end

function EvObj:Insert( Str, Callback )
	Framework.Comm.InsertEvent( self.App, self.Unique, Str, Callback )
end

function EvObj:Count( Callback )
	Framework.Comm.CountEvents( self.App, self.Unique, Callback )
end

function EvObj:Get( Limit, Callback )
	Framework.Comm.GetEvents( self.App, self.Unique, Limit, Callback )
end

function EvObj:Pop( Limit, Callback )
	Framework.Comm.PopEvents( self.App, self.Unique, Limit, Callback )
end